{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Morse Runner\n",
    "\n",
    "rev 0.1  \n",
    "按照摩斯码长（难度），从指定的字母集中生成随机训练字符并发音。  \n",
    "\n",
    "## Release History\n",
    "- rev 0.1: [Stored]实现练习轮数功能\n",
    "- rev 0.0: 基于 winsound 实现了基本的发音功能；单词生成"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random, winsound, time, sys\n",
    "\n",
    "randomInitialState = random.getstate() # 储存初始状态，便于后续复现\n",
    "\n",
    "'''\n",
    "alphabet 存储字母的难易信息\n",
    "使用空格将电码等长的放在一组\n",
    "字符串输入方便，而后转换为列表使用方便\n",
    "'''\n",
    "\n",
    "alphabet = \"ET IMNA SOR 12345 67890\"\n",
    "# 删除空格并转换为列表\n",
    "alphabet = list(alphabet.replace(' ', ''))\n",
    "\n",
    "'''\n",
    "codeMap 存储字母到摩斯码的映射关系\n",
    "多行代码可以使用C风格的斜杠；由于此处有{}，也可不加\n",
    "'''\n",
    "# 手动输入字母部分\n",
    "codeMap = {'E': '.', 'T': '-', \\\n",
    "           'I': '..', 'A': '.-', 'N': '-.', 'M': '--', \\\n",
    "           'S': '...', 'O': '---', 'R': '.-.', \\\n",
    "           'D': '-..', 'G': '--.', 'U': '..-', 'W': '.--', \\\n",
    "           'B': '-...', 'C': '-.-.', 'J': '.---'}\n",
    "# 生成数字1-5\n",
    "for i in range(1,6):\n",
    "    numberCode = ['.' for j in range(i)]\n",
    "    numberCode = numberCode + ['-' for j in range(5-i)]\n",
    "    codeMap.update({str(i): ''.join(numberCode)})\n",
    "# 生成数字6-9\n",
    "for i in range(6,10):\n",
    "    numberCode = ['-' for j in range(i-5)]\n",
    "    numberCode = numberCode + ['.' for j in range(10-i)]\n",
    "    codeMap.update({str(i): ''.join(numberCode)})\n",
    "# 生成数字0\n",
    "codeMap.update({'0': '-----'})\n",
    "\n",
    "# frequency in Hz\n",
    "pitch = 580 # Code Tone\n",
    "# timing in ms or ms/1000, i.e. s\n",
    "t_dit = 100 # dit duration\n",
    "t_dah = 280 # dah duration\n",
    "t_sep = 80/1000 # blanking between dits and dahs\n",
    "t_alph = 600/1000 # blanking between chars\n",
    "t_word = 1000/1000 # blanking between words"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generateWord(alphaCount, alphaRange):\n",
    "    strList = []\n",
    "    for i in range(alphaCount):\n",
    "        strList.append( alphabet[random.randint(0,alphaRange)] )\n",
    "    return \"\".join(strList) # 'ab'\n",
    "    #return strList            # ['a', 'b']\n",
    "\n",
    "# Note:\n",
    "# winsound 接收 int 型 ms 时间\n",
    "# 而 time.sleep 接收 float 型 s 时间\n",
    "\n",
    "# 句子发音：句子形如'''ab cdd efgh ijk?'''，标点符号分隔句子\n",
    "# 包括句间停顿在内的所有时序都在此函数中实现\n",
    "def generateSound(sentence):\n",
    "    alpha_code = ''\n",
    "#     print('sentence = '+ sentence)\n",
    "    for alpha in sentence:\n",
    "        # 遍历一个句子内的所有字母\n",
    "        \n",
    "        alpha_code = codeMap.get(str(alpha.upper()))\n",
    "        \n",
    "        # 该字母可能没有在码表收录，或是遇到空格等\n",
    "        if alpha_code == None:\n",
    "            sys.stdout.write(alpha)\n",
    "            time.sleep(t_sep)\n",
    "        else:\n",
    "            for elements in alpha_code:\n",
    "                # 遍历一个字母的所有嘀嗒\n",
    "                if elements == '.':\n",
    "                    winsound.Beep(pitch, t_dit)\n",
    "                    sys.stdout.write('.')\n",
    "                else:\n",
    "                    winsound.Beep(pitch, t_dah)\n",
    "                    sys.stdout.write('-')\n",
    "                    \n",
    "                sys.stdout.write('') # 虽然时序上嘀嗒间需要时延，但不需要打印出来\n",
    "                time.sleep(t_intra_char)\n",
    "                 # 一个字母的一个嘀嗒遍历完毕\n",
    "        sys.stdout.write(' ')\n",
    "        time.sleep(t_inter_char - t_intra_char)\n",
    "        # 一个字母的所有嘀嗒遍历完毕\n",
    "    sys.stdout.write(' ')\n",
    "    time.sleep(t_word - t_inter_char)\n",
    "    # 一个单词内的所有字母遍历完毕\n",
    "    return"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "第1轮, 输入[Enter]以开始:\n",
      "-. ..   - -   ..   --- .. .. ..   - ... -- .   .-. --- -- -.    \n",
      "抄收内容:>\n",
      "播放内容:>NI TT I OIII TSME ROMN \n",
      "\n",
      "第2轮, 输入[Enter]以开始:\n",
      "-- ..   .-. . ---   .- . -.   ... -. .-.   --- ---   ..    "
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "Interrupted by user",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m----------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                    Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-23-54dee67056bd>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     33\u001b[0m     \u001b[0mgenerateSound\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mword\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     34\u001b[0m     \u001b[0mtxDat\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mtxDat\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mlist\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mword\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 35\u001b[1;33m     \u001b[0mrxDat\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mrxDat\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mlist\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minput\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'\\n抄收内容:>'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     36\u001b[0m     \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'播放内容:>'\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mword\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     37\u001b[0m     \u001b[0mword\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m''\u001b[0m \u001b[1;31m# 使用完要清零\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Anaconda3\\lib\\site-packages\\ipykernel\\kernelbase.py\u001b[0m in \u001b[0;36mraw_input\u001b[1;34m(self, prompt)\u001b[0m\n\u001b[0;32m    858\u001b[0m                 \u001b[1;34m\"raw_input was called, but this frontend does not support input requests.\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    859\u001b[0m             )\n\u001b[1;32m--> 860\u001b[1;33m         return self._input_request(str(prompt),\n\u001b[0m\u001b[0;32m    861\u001b[0m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_parent_ident\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    862\u001b[0m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_parent_header\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mD:\\Anaconda3\\lib\\site-packages\\ipykernel\\kernelbase.py\u001b[0m in \u001b[0;36m_input_request\u001b[1;34m(self, prompt, ident, parent, password)\u001b[0m\n\u001b[0;32m    902\u001b[0m             \u001b[1;32mexcept\u001b[0m \u001b[0mKeyboardInterrupt\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    903\u001b[0m                 \u001b[1;31m# re-raise KeyboardInterrupt, to truncate traceback\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 904\u001b[1;33m                 \u001b[1;32mraise\u001b[0m \u001b[0mKeyboardInterrupt\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Interrupted by user\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    905\u001b[0m             \u001b[1;32mexcept\u001b[0m \u001b[0mException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    906\u001b[0m                 \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwarning\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Invalid Message:\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mexc_info\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: Interrupted by user"
     ]
    }
   ],
   "source": [
    "# frequency in Hz\n",
    "pitch = 580 # Code Tone\n",
    "# Words per Minute\n",
    "wpm = 20\n",
    "# timing in ms or ms/1000, i.e. s\n",
    "t_unit = int(60 * 1000 / (50 * wpm)) # unit time in ms. word 'PARIS' has 50 units\n",
    "t_dit = t_unit # dit duration\n",
    "t_dah = 3 * t_unit # dah duration\n",
    "t_intra_char = t_unit / 1000 # blanking between dits and dahs\n",
    "t_inter_char = 3 * t_unit / 1000 # blanking between chars\n",
    "t_word = 7 * t_unit / 1000 # blanking between words\n",
    "\n",
    "word_count = 6\n",
    "word_len = 3\n",
    "word_len_dev = 2\n",
    "word = '';\n",
    "\n",
    "testRound = 5\n",
    "txDat = []\n",
    "rxDat = []\n",
    "\n",
    "# randomInitialState = random.getstate()\n",
    "# random.setstate(randomInitialState)\n",
    "\n",
    "for test_round in range(testRound):\n",
    "    for i in range(word_count):\n",
    "    #     wordLength = int(word_len + word_len_dev*(2*random.random() - 1))\n",
    "        wordLength = int(random.uniform(word_len - word_len_dev, word_len + word_len_dev))\n",
    "        word = word + generateWord(wordLength, 8)\n",
    "        word = word + ' '\n",
    "    input('\\n第' + str(test_round + 1) + '轮, 输入[Enter]以开始:')\n",
    "    time.sleep(1)\n",
    "    generateSound(word)\n",
    "    txDat = txDat + list(word)\n",
    "    rxDat = rxDat + list(input('\\n抄收内容:>'))\n",
    "    print('播放内容:>' + word)\n",
    "    word = '' # 使用完要清零"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
